/**
 * @file board.c
 * @author Hello (you@domain.com)
 * @brief 
 * @version 0.1
 * @date 2021-09-12
 * 
 * @copyright Copyright (c) 2021
 * 
 */
#include <stdint.h>
#include <stdbool.h>
#include <serial.h>

/**
 * @brief CCM (Clock Control Module) register block structure
 */
typedef struct
{
    uint32_t CPU_BUS_CLKCFG;       /* offset: 0x00, CPU bus clock configure register */
    uint32_t BUS_PERIPH_CLK_CTRL;  /* offset: 0x04, Bus device clock gating control Register */
    uint32_t BUS_PERIPH_RST_CTRL;  /* offset: 0x08, Bus device reset control register */
    uint32_t RESERVED0[4];
    uint32_t SDC1_MCLK_CTRL;       /* offset: 0x1C, SDC1 clock control register */
    uint32_t SPI0_MCLK_CTRL;       /* offset: 0x20, SPI0 clock control register */
    uint32_t SPI1_MCLK_CTRL;       /* offset: 0x24, SPI1 clock control register */
    uint32_t SDC0_MCLK_CTRL;       /* offset: 0x28, SDC0 clock control register */
    uint32_t CE_MCLK_CTRL;         /* offset: 0x2C, CE clock control register */
    uint32_t CSI_JPE_DEV_CLK_CTRL; /* offset: 0x30, CSI JPEG module clock control register */
    uint32_t DAUDIO_MCLK_CTRL;     /* offset: 0x34, Digital audio clock control register */
    uint32_t IRRX_MCLK_CTRL;       /* offset: 0x38, IRRX clock control register */
    uint32_t IRTX_MCLK_CTRL;       /* offset: 0x3C, IRTX clock control register */
    uint32_t SYSTICK_REFCLK_CTRL;  /* offset: 0x40, System tick reference clock register */
    uint32_t SYSTICK_CALIB_CTRL;   /* offset: 0x44, System tick clock calibration register */
    uint32_t DMIC_MCLK_CTRL;       /* offset: 0x48, DMIC clock control register */
    uint32_t GPADC_MCLK_CTRL;      /* offset: 0x4C, GPADC clock control register */
    uint32_t CSI_MCLK_CTRL;        /* offset: 0x50, CSI output clock control register */
    uint32_t FLASHC_MCLK_CTRL;     /* offset: 0x54, Flash controller clock control register */
    uint32_t AUDIO_CODEC_MCLK_CTRL;/* offset: 0x58, AUDIO module clock control register */
    uint32_t APBS_CLKCFG;          /* offset: 0x5C, Special APB clock configure register (UART/PRCM/RTC APB bus clock) */
    uint32_t PSRAMC_MCLK_CTRL;     /* offset: 0x60, PSRAM controller clock control register */
} CCM_T;

/**
 * @brief GPIO control register block structure
 */
typedef struct {
    uint32_t MODE[4];      /* offset: 0x00, GPIO working mode configuration register */
    uint32_t DATA;         /* offset: 0x10, GPIO data register */
    uint32_t DRIVING[2];   /* offset: 0x14, GPIO driving level register */
    uint32_t PULL[2];      /* offset: 0x1C, GPIO pull type register */
} GPIO_CTRL_T;

/**
 * @brief UART register block structure
 */
typedef struct {
    union {
        uint32_t RX_BUF;           /* offset: 0x00, UART receive buffer register, 8-bit valid, RO */
        uint32_t TX_HOLD;          /* offset: 0x00, UART transmit holding register, 8-bit valid, WO */
        uint32_t DIV_LOW;          /* offset: 0x00, UART divisor latch low register, 8-bit valid, R/W */
    } RBR_THR_DLL;                      /* offset: 0x00, UART receive buffer/transmit holding/divisor latch low register */
    union {
        uint32_t DIV_HIGH;         /* offset: 0x04, UART divisor latch high register, 8-bit valid, R/W */
        uint32_t IRQ_EN;           /* offset: 0x04, UART interrupt enable register, R/W */
    } DLH_IER;                          /* offset: 0x04, UART divisor latch high/IRQ enable register */
    union {
        uint32_t IRQ_ID;           /* offset: 0x08, UART interrupt identity register, RO */
        uint32_t FIFO_CTRL;        /* offset: 0x08, UART FIFO control register, WO */
    } IIR_FCR;                          /* offset: 0x08, UART interrupt identity/FIFO control register */
    uint32_t LINE_CTRL;            /* offset: 0x0C, UART line control register */
    uint32_t MODEM_CTRL;           /* offset: 0x10, UART modem control register */
    uint32_t LINE_STATUS;          /* offset: 0x14, UART line status register */
    uint32_t MODEM_STATUS;         /* offset: 0x18, UART modem stauts register */
    uint32_t SCRATCH;              /* offset: 0x1C, UART scratch register */
    uint32_t RESERVED1[23];
    uint32_t STATUS;               /* offset: 0x7C, UART status register */
    uint32_t TX_FIFO_LEVEL;        /* offset: 0x80, UART transmit FIFO level register */
    uint32_t RX_FIFO_LEVEL;        /* offset: 0x84, UART receive FIFO level register */
    uint32_t RESERVED2[7];
    uint32_t HALT;                 /* offset: 0xA4, UART halt register */
    uint32_t RESERVED3[9];
    uint32_t TX_DELAY;             /* offset: 0xCC, UART TX delay register */
    uint32_t RESERVED4[1];
    uint32_t BAUD_DECT_CTRL;       /* offset: 0xD4, UART baudrate detection control register */
    uint32_t BAUD_DECT_VAL_LOW;    /* offset: 0xD8, UART baudrate detection counter low register */
    uint32_t BAUD_DECT_VAL_HIGH;   /* offset: 0xDC, UART baudrate detection counter high register */
} UART_T;

/** \brief  Structure type to access the System Timer (SysTick).
 */
typedef struct
{
  uint32_t CTRL;                    /*!< Offset: 0x000 (R/W)  SysTick Control and Status Register */
  uint32_t LOAD;                    /*!< Offset: 0x004 (R/W)  SysTick Reload Value Register       */
  uint32_t VAL;                     /*!< Offset: 0x008 (R/W)  SysTick Current Value Register      */
  uint32_t CALIB;                   /*!< Offset: 0x00C (R/ )  SysTick Calibration Register        */
} SysTick_Type;

static volatile CCM_T *ccm = (CCM_T *)0x40040400;
static volatile UART_T *uart0 = (UART_T *)0x40040C00;
static volatile GPIO_CTRL_T *gpiob = (GPIO_CTRL_T *)0x40050024;
static volatile SysTick_Type *tick = (SysTick_Type *)0xE000E010;

#define APB_FREQ	40000000	// 40MHz

int board_init(void)
{
	// volatile unsigned long int *systick = (volatile unsigned long int *)0x40040440;
	// *systick |= 0x1 << 31;

	// systick-timer.c read it!
	// cpu clock
	tick->CTRL |= 0x1<<2;
	return 0;
}

void led_init(void)
{
	/* PB2 -> led */

	/* enable gpio clock */
	ccm->BUS_PERIPH_CLK_CTRL |= 0x1<<27;

	/* set working mode (function) */
	gpiob->MODE[0] &= ~(0xF<<8);
	gpiob->MODE[0] |= 0x1 << 8;
	
	/* set driving */
	gpiob->DRIVING[0] &= ~(0x3<<4);
	gpiob->DRIVING[0] |= 0x1 << 4;	// GPIO_DRIVING_LEVEL_1

	/* set pull */
	gpiob->PULL[0] &= ~(0x3<<4);
	gpiob->PULL[0] |= 0x1 << 4;	// GPIO_PULL_UP

	/* to high */
	// gpiob->DATA |= 0x1 << 2;

	/* set to low is off */
	gpiob->DATA &= ~(0x1<<2);
}

void led_set(uint8_t status)
{
	uint32_t value = gpiob->DATA;
	value &= ~(0x1<<2);
	value |= !!status << 2;
	gpiob->DATA = value;
}

void uart0_init(void)
{
	uint16_t div;

	/* enable uart clock and release reset */
	ccm->BUS_PERIPH_CLK_CTRL |= 0x1<<16;
	ccm->BUS_PERIPH_RST_CTRL |= 0x1<<16;

	/* disable all IRQ */
	uart0->DLH_IER.IRQ_EN = 0;

	/* disable tx */
	uart0->HALT |= 0x1;

	/* start configrution */
	uart0->HALT |= 0x1<<1;

	/* 
	 * set baud rate:
	 * baudRate = apb_freq / (16 * div) 
	 * div = apb_freq / (16 * baudRate)
	 */
	div = (uint16_t)(APB_FREQ / (float)(16 * 115200) + 0.5);
	uart0->LINE_CTRL |= 0x1<<7;
	uart0->RBR_THR_DLL.DIV_LOW = div & 0xFF;
	uart0->DLH_IER.DIV_HIGH = div>>8 & 0xFF;
	uart0->LINE_CTRL &= ~(0x1<<7);

	/* set parity, stop bits, data bits */
	uart0->LINE_CTRL |= 0x3;	// data:8bit, parity:disabled, stop:1bit

	/* stop configrution */
	uart0->HALT |= 0x1<<2;
	while (uart0->HALT & 0x1<<2);
	uart0->HALT &= ~(0x1<<1);

	/* enable tx */
	uart0->HALT &= ~0x1;
}

void uart0_gpio_init(void)
{
	/* PB0 -> TX   |    PB1 -> RX */

	/* enable gpio clock */
	ccm->BUS_PERIPH_CLK_CTRL |= 0x1<<27;

	/* set working mode (function) */
	gpiob->MODE[0] &= ~0xFF;
	gpiob->MODE[0] |= 0x22;	// GPIOB_P0 -> UART0_TX     GPIOB_P1 -> UART0_RX
	
	/* set driving */
	gpiob->DRIVING[0] &= ~0xF;
	gpiob->DRIVING[0] |= 0x5; // GPIO_DRIVING_LEVEL_1

	/* set pull */
	gpiob->PULL[0] &= ~0xF;
	gpiob->PULL[0] |= 0x5;	  // GPIO_PULL_UP
}


void uart0_putchar(const char ch)
{
	if(ch == '\n') uart0_putchar('\r');
	/* wati transmit complete */
	while(!(uart0->LINE_STATUS & 0x1<<6));
	/* send data */
	uart0->RBR_THR_DLL.TX_HOLD = ch;
}


void uart0_puts(const char *s)
{
	char *str = (char *)s;
	while(*str) uart0_putchar(*str++);
	// uart0_putchar('\r');
	// uart0_putchar('\n');
}

static int xr872_uart0_start(void)
{
	led_init();
	uart0_gpio_init();
	uart0_init();
	led_set(1);
	return 0;
}

static int xr872_uart0_stop(void)
{
	return 0;
}

/**
 * @brief set uart
 * @param void
 * @return void
 */
static void xr872_uart0_setbrg(void)
{
	/* Do nothing */
}

/**
 * @brief get ones char
 * 
 * @return int 
 */
static int xr872_uart0_getc(void)
{
	/* read buffer */
	return (int)uart0->RBR_THR_DLL.RX_BUF;
}

/**
 * @brief detection input
 * 
 * @return int 
 */
static int xr872_uart0_tstc(void)
{
	/* receiver data ready flag */
	return (int)(uart0->LINE_STATUS & 0x1<<0);
}

static void xr872_uart0_putc(const char c)
{
	uart0_putchar(c);
}

static void xr872_uart0_puts(const char *s)
{
	uart0_puts(s);
}

static struct serial_device xr872_uart0 = {
	.name = "xr872_uart0",
	.start = xr872_uart0_start,
	.stop = xr872_uart0_stop,
	.setbrg = xr872_uart0_setbrg,
	.getc = xr872_uart0_getc,
	.tstc = xr872_uart0_tstc,
	.putc = xr872_uart0_putc,
	.puts = xr872_uart0_puts,
	.next = (struct serial_device *)0
};

struct serial_device *default_serial_console(void)
{
    return &xr872_uart0;
}
